package model

import (
	"appengine"
	"appengine/datastore"
	"appengine/memcache"
	"entity"
	"strconv"
	"time"
)

var NewsLimit int = 6
var News_CacheName string = "_news_"
var HomePage_CacheName string = "_homePage_"
var RecentView_CacheName string = "_recentView_"

func AddNews(c appengine.Context, obj entity.News) error {
	cacheTitle := News_CacheName + obj.Alias
	key := datastore.NewKey(c, "News", obj.Alias, 0, nil)
	_, err := datastore.Put(c, key, &obj)
	if err != nil {
		return err
	}
	itemCache := &memcache.Item{
		Key:    cacheTitle,
		Object: obj,
	}
	memcache.Gob.Set(c, itemCache)
	//remove homepage cache
	for i := 0; i < 4; i++ {
		strPage := strconv.Itoa(i)
		memcache.Delete(c, HomePage_CacheName+strPage)
	}
	memcache.Delete(c, RecentView_CacheName)
	memcache.Delete(c, ListCategory_CacheName)
	memcache.Delete(c, TagPool_CacheName)
	return nil
}

func DeleteNews(c appengine.Context, id string) error {
	cacheTitle := News_CacheName + id
	key := datastore.NewKey(c, "News", id, 0, nil)
	if err := datastore.Delete(c, key); err != nil {
		return err
	} else {
		memcache.Delete(c, cacheTitle)
		//remove homepage cache
		for i := 0; i < 4; i++ {
			strPage := strconv.Itoa(i)
			memcache.Delete(c, HomePage_CacheName+strPage)
		}
		//remove relation cache
		memcache.Delete(c, RecentView_CacheName)
		memcache.Delete(c, ListCategory_CacheName)
		memcache.Delete(c, TagPool_CacheName)
		return nil
	}
}

func GetNews(c appengine.Context, id string, obj *entity.News) error {
	cacheTitle := News_CacheName + id
	if _, err := memcache.Gob.Get(c, cacheTitle, &obj); err == memcache.ErrCacheMiss {
		k := datastore.NewKey(c, "News", id, 0, nil)
		if err := datastore.Get(c, k, obj); err != nil {
			return err
		}
		itemCache := &memcache.Item{
			Key:    cacheTitle,
			Object: obj,
		}
		memcache.Gob.Set(c, itemCache)
	}
	return nil
}

func ListNews(c appengine.Context, page int) (arr []entity.News, err error) {
	page = page - 1
	cacheTitle := HomePage_CacheName + strconv.Itoa(page)
	if _, err := memcache.Gob.Get(c, cacheTitle, &arr); err == memcache.ErrCacheMiss {
		//To check if there is next page, get one more item from query, so limit = limit + 1
		q := datastore.NewQuery("News").Order("-doc").Limit(NewsLimit + 1).Offset(NewsLimit * page)
		if _, err := q.GetAll(c, &arr); err != nil {
			return arr, err
		}
		itemCache := &memcache.Item{
			Key:    cacheTitle,
			Object: arr,
		}
		memcache.Gob.Set(c, itemCache)
	}
	return arr, nil
}

func ListNewsByTime(c appengine.Context, timeQuery time.Time, page int) (arr []entity.News, err error) {
	page = page - 1
	cacheTitle := HomePage_CacheName + timeQuery.Format(time.RFC850) + strconv.Itoa(page)
	if _, err := memcache.Gob.Get(c, cacheTitle, &arr); err == memcache.ErrCacheMiss {
		//To check if there is next page, get one more item from query, so limit = limit + 1
		maxTime := time.Date(timeQuery.Year(), timeQuery.Month(), timeQuery.Day()+1, 0, 0, 0, 0, timeQuery.Location())
		minTime := time.Date(timeQuery.Year(), timeQuery.Month(), timeQuery.Day()-1, 23, 59, 59, 0, timeQuery.Location())
		q := datastore.NewQuery("News").Filter("doc >", minTime).Filter("doc <", maxTime).Order("-doc").Limit(NewsLimit + 1).Offset(NewsLimit * page)
		if _, err := q.GetAll(c, &arr); err != nil {
			return arr, err
		}
		itemCache := &memcache.Item{
			Key:    cacheTitle,
			Object: arr,
		}
		memcache.Gob.Set(c, itemCache)
	}
	return arr, nil
}

func GetNextNews(c appengine.Context, currentObj entity.News, obj *entity.News) (exist bool, err error) {
	cacheTitle := "_nextNews_" + currentObj.Alias
	if _, err := memcache.Gob.Get(c, cacheTitle, &obj); err == memcache.ErrCacheMiss {
		var arr []entity.News
		q := datastore.NewQuery("News").Filter("doc >", currentObj.Doc).Limit(1)
		if _, err := q.GetAll(c, &arr); err != nil {
			exist = false
			return exist, nil
		}

		if len(arr) > 0 {
			CloneNews(arr[0], obj)
		} else {
			exist = false
			return exist, nil
		}
		//Next item cache for this news
		itemCache := &memcache.Item{
			Key:    cacheTitle,
			Object: obj,
		}
		memcache.Gob.Set(c, itemCache)
		//Detail cache
		itemCache = &memcache.Item{
			Key:    News_CacheName + obj.Alias,
			Object: obj,
		}
		memcache.Gob.Set(c, itemCache)
	}
	exist = true
	return exist, nil
}

func GetPreviousNews(c appengine.Context, currentObj entity.News, obj *entity.News) (exist bool, err error) {
	var cacheTitle string = "_previousNews_" + currentObj.Alias
	if _, err := memcache.Gob.Get(c, cacheTitle, &obj); err == memcache.ErrCacheMiss {
		var arr []entity.News
		q := datastore.NewQuery("News").Order("-doc").Filter("doc <", currentObj.Doc).Limit(1)
		if _, err := q.GetAll(c, &arr); err != nil {
			exist = false
			return exist, nil
		}

		if len(arr) > 0 {
			CloneNews(arr[0], obj)
		} else {
			exist = false
			return exist, nil
		}
		//Previous item cache for this news
		itemCache := &memcache.Item{
			Key:    cacheTitle,
			Object: obj,
		}
		memcache.Gob.Set(c, itemCache)
		//Detail cache
		itemCache = &memcache.Item{
			Key:    News_CacheName + obj.Alias,
			Object: obj,
		}
		memcache.Gob.Set(c, itemCache)
	}
	exist = true
	return exist, nil
}

func CloneNews(obj1 entity.News, obj2 *entity.News) {
	obj2.Alias = obj1.Alias
	obj2.Title = obj1.Title
	obj2.Description = obj1.Description
	obj2.Content = obj1.Content
	obj2.Source = obj1.Source
	obj2.Thumb = obj1.Thumb
	obj2.Doc = obj1.Doc
	obj2.Category = obj1.Category
	obj2.Tags = obj1.Tags
}

func ListRecentNews(c appengine.Context) (arr []entity.News, err error) {
	if _, err := memcache.Gob.Get(c, RecentView_CacheName, &arr); err == memcache.ErrCacheMiss {
		q := datastore.NewQuery("News").Order("doc").Limit(NewsLimit)
		if _, err := q.GetAll(c, &arr); err != nil {
			return arr, err
		}
		itemCache := &memcache.Item{
			Key:    RecentView_CacheName,
			Object: arr,
		}
		memcache.Gob.Set(c, itemCache)
	}
	return arr, nil
}

func AddRecentNews(c appengine.Context, obj entity.News) error {
	var arr []entity.News
	var arrRecent []entity.News
	if _, err := memcache.Gob.Get(c, RecentView_CacheName, &arr); err == memcache.ErrCacheMiss {
		q := datastore.NewQuery("News").Order("doc").Limit(NewsLimit)
		if _, err := q.GetAll(c, &arr); err != nil {
			return err
		}
	}

	var fixedLength int = 10
	var arrLength int = len(arr)
	var startItem int = 0
	if arrLength > fixedLength {
		startItem = arrLength - fixedLength
	}

	for i := startItem; i < arrLength; i++ {
		if arr[i].Alias == obj.Alias {
			continue
		} else {
			arrRecent = append(arrRecent, arr[i])
		}
	}

	arrRecent = append(arrRecent, obj)

	itemCache := &memcache.Item{
		Key:    RecentView_CacheName,
		Object: arrRecent,
	}
	memcache.Gob.Set(c, itemCache)
	return nil
}

func GetMultiNews(c appengine.Context, arrNewsAlias []string) (arr []entity.News, err error) {
	var keys []*datastore.Key

	for _, e := range arrNewsAlias {
		k := datastore.NewKey(c, "News", e, 0, nil)
		keys = append(keys, k)
	}
	//arrTemp has to be build with length because of datastore.GetMulti's required
	arrTemp := make([]entity.News, len(keys), len(keys))
	if err := datastore.GetMulti(c, keys, arrTemp); err != nil {
		if me, ok := err.(appengine.MultiError); ok {
			var tempKeys []*datastore.Key
			for i, merr := range me {
				if merr != datastore.ErrNoSuchEntity {
					tempKeys = append(tempKeys, keys[i])
				}
			}
			//Rebuild array because of keys's length has been change
			arrTemp = make([]entity.News, len(tempKeys), len(tempKeys))
			if err := datastore.GetMulti(c, tempKeys, arrTemp); err != nil {
				return arr, err
			}
			arr = arrTemp
		} else {
			return arr, err
		}
	} else {
		arr = arrTemp
	}
	//Cache all item for detail page
	for _, e := range arr {
		itemCache := &memcache.Item{
			Key:    News_CacheName + e.Alias,
			Object: e,
		}
		memcache.Gob.Set(c, itemCache)
	}
	return arr, nil
}

func CheckExistNews(c appengine.Context, id string) bool {
	k := datastore.NewKey(c, "News", id, 0, nil)
	if err := datastore.Get(c, k, nil); err == datastore.ErrNoSuchEntity {
		return false
	} else {
		return true
	}
}
